package ch.ti.bfh.app.loodel.internal;

import ch.ti.bfh.app.loodel.domain.member.Member;
import ch.ti.bfh.app.loodel.domain.member.QMember;
import ch.ti.bfh.app.loodel.domain.timeslot.QLocation;
import ch.ti.bfh.app.loodel.domain.timeslot.TimeSlot;
import ch.ti.bfh.app.loodel.domain.timeslotvote.QTimeSlotVote;
import ch.ti.bfh.app.loodel.internal.security.InternalUserContext;
import ch.ti.bfh.app.loodel.service.MemberData;
import ch.ti.bfh.app.loodel.service.QMemberData;
import ch.ti.bfh.app.loodel.service.create.QShowTimeSlotLocationData;
import ch.ti.bfh.app.loodel.service.create.ShowTimeSlotLocationData;
import ch.ti.bfh.app.loodel.service.create.TimeSlotVoteSearchService;
import com.mysema.query.jpa.impl.JPAQuery;
import com.mysema.query.jpa.impl.JPASubQuery;
import com.mysema.query.types.expr.BooleanExpression;
import com.mysema.query.types.query.ListSubQuery;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

@Service
@Transactional
public class DefaultTimeSlotVoteSearchService implements TimeSlotVoteSearchService {

    @PersistenceContext
    private EntityManager entityManager;

    private final InternalUserContext userContext;

    public static final int DEFAULT_PAGE_SIZE = 10;

    private static PageRequest DEFAULT_PAGE_REQUEST = new PageRequest(0, DEFAULT_PAGE_SIZE);

    @Inject
    public DefaultTimeSlotVoteSearchService(InternalUserContext userContext) {
        this.userContext = userContext;
    }

    @Override
    public List<ShowTimeSlotLocationData> findTimeSlotLocationsByCurrentMemberTimeSlotVotes(String searchTerm) {
        Long currentMemberId = userContext.getCurrentMember().getId();
        QTimeSlotVote timeSlotVote = QTimeSlotVote.timeSlotVote;
        QLocation timeSlotVoteLocation = timeSlotVote.timeSlot.location;
        JPAQuery query = new JPAQuery(entityManager).from(timeSlotVote).
                where(timeSlotVote.voter.id.eq(currentMemberId).and(anyLocationAttributeMatches(timeSlotVoteLocation, searchTerm)));
        applyPaging(query);
        return query.distinct().list(new QShowTimeSlotLocationData(timeSlotVoteLocation.place, timeSlotVoteLocation.street, timeSlotVoteLocation.name));
    }

    private void applyPaging(JPAQuery query) {
        query.offset(DEFAULT_PAGE_REQUEST.getOffset());
        query.limit(DEFAULT_PAGE_REQUEST.getPageSize());
    }

    @Override
    public List<MemberData> findMembersWithSameTimeSlotVotesAsCurrentMember(String memberSearchString) {
        final Member member = userContext.getCurrentMember();
        QTimeSlotVote timeSlotVote = QTimeSlotVote.timeSlotVote;
        final ListSubQuery<TimeSlot> allTimeSlotsForCurrentMember = new JPASubQuery().from(timeSlotVote).where(timeSlotVote.voter.eq(member)).list(timeSlotVote.timeSlot);
        BooleanExpression whereQuery = timeSlotVote.timeSlot.in(allTimeSlotsForCurrentMember).and(timeSlotVote.voter.ne(member));
        if (StringUtils.isNotEmpty(memberSearchString)) {
            whereQuery = whereQuery.and(anyMemberAttributeMatches(timeSlotVote.voter, memberSearchString));
        }
        JPAQuery query = new JPAQuery(entityManager).from(timeSlotVote).where(whereQuery);
        applyPaging(query);
        return query.distinct().list(new QMemberData(timeSlotVote.voter.email, timeSlotVote.voter.fullName));
    }

    private BooleanExpression anyLocationAttributeMatches(QLocation qLocation, String searchTerm) {
        String str = buildLikeTerm(searchTerm);
        return qLocation.name.lower().like(str).or(qLocation.place.lower().like(str)).or(qLocation.street.lower().like(str));
    }

    private BooleanExpression anyMemberAttributeMatches(QMember qMember, String searchTerm) {
        String str = buildLikeTerm(searchTerm);
        return qMember.firstName.lower().like(str).or(qMember.lastName.lower().like(str)).or(qMember.email.like(str));
    }

    private String buildLikeTerm(String searchTerm) {
        return "%" + searchTerm.toLowerCase() + "%";
    }
}
